/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.ui.editor.advanced;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.daikit.daikit4gxt.client.DkBaseMain;
import com.daikit.daikit4gxt.client.propertyaccess.DkEditDeletePropertyAccess;
import com.daikit.daikit4gxt.client.ui.UIInvalidatable;
import com.daikit.daikit4gxt.client.ui.cell.IconButtonCell;
import com.daikit.daikit4gxt.client.ui.cell.ListStoreIconButtonCell;
import com.daikit.daikit4gxt.client.ui.editor.field.DkComboBox;
import com.daikit.daikit4gxt.client.ui.editor.field.DkDynamicAsyncPagingCombobox;
import com.daikit.daikit4gxt.client.utils.miscs.DkDelayedExecution;
import com.daikit.daikit4gxt.client.utils.miscs.DkTimerUtils;
import com.daikit.daikit4gxt.client.utils.miscs.DkUIUtils;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.event.StoreAddEvent;
import com.sencha.gxt.data.shared.event.StoreAddEvent.StoreAddHandler;
import com.sencha.gxt.data.shared.event.StoreClearEvent;
import com.sencha.gxt.data.shared.event.StoreClearEvent.StoreClearHandler;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.ComboBox;
import com.sencha.gxt.widget.core.client.form.Field;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.tips.QuickTip;
import com.sencha.gxt.widget.core.client.toolbar.FillToolItem;
import com.sencha.gxt.widget.core.client.toolbar.ToolBar;


/**
 * Reference Selector for for bean List property. (grid with top toolbar with dropdown and add button)
 * 
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 * @param <M>
 */
public abstract class DkReferenceEditorGrid<M extends Serializable> extends AbstractDkHideableAdapterField<List<M>> implements
		UIInvalidatable
{

	private final Grid<M> grid;
	private final ListStore<M> store;
	private final IconButtonCell deleteCell;
	private final TextButton buttonAdd;
	private Field<M> selectionField;
	private boolean headerVisible = false;
	private boolean enabled = true;

	private final VerticalLayoutContainer verticalLayoutContainer;

	/**
	 * Constructor
	 * 
	 * @param listStore
	 *           the {@link ListStore}
	 * @param columnConfigs
	 *           the {@link ColumnConfig}s
	 * @param props
	 *           a {@link PropertyAccess} for edited bean
	 */
	public DkReferenceEditorGrid(final ListStore<M> listStore, final List<ColumnConfig<M, ?>> columnConfigs,
			final DkEditDeletePropertyAccess<M> props)
	{
		super(new ContentPanel());
		verticalLayoutContainer = new VerticalLayoutContainer();
		getContentPanel().add(verticalLayoutContainer);
		getContentPanel().setHeaderVisible(false);
		setHeight(DkBaseMain.config().getEditorGridHeight());

		this.store = listStore;

		// this because driver calls setValue directly on the store.
		store.addStoreClearHandler(new StoreClearHandler<M>()
		{
			@Override
			public void onClear(final StoreClearEvent<M> event)
			{
				resetFieldSelection();
			}
		});
		// this because driver calls setValue directly on the store.
		store.addStoreAddHandler(new StoreAddHandler<M>()
		{
			@Override
			public void onAdd(final StoreAddEvent<M> event)
			{
				resetFieldSelection();
			}
		});

		final List<ColumnConfig<M, ?>> columns = new ArrayList<ColumnConfig<M, ?>>(columnConfigs);
		final ColumnConfig<M, String> columnDelete = new ColumnConfig<M, String>(props.delete(), 30, "");
		columnDelete.setMenuDisabled(true);
		columnDelete.setResizable(false);
		columnDelete.setSortable(false);
		columnDelete.setFixed(true);
		deleteCell = new ListStoreIconButtonCell<M>(DkBaseMain.icons().delete2_16(), getDeleteTooltip(), listStore)
		{
			@Override
			protected void onClick(final M model, final int column, final int row, final String modelKey)
			{
				if (isEnabled())
				{
					onDeleteButtonClicked(model);
				}
			}

			@Override
			protected boolean isCellIconVisible(final M model, final int column, final int row, final String modelKey)
			{
				return isModelDeletable(model, column, row, modelKey);
			}
		};
		columnDelete.setCell(deleteCell);
		columns.add(columnDelete);

		final ColumnModel<M> cm = new ColumnModel<M>(columns);

		grid = new Grid<M>(listStore, cm);
		new QuickTip(grid);
		verticalLayoutContainer.setBorders(false);

		final ToolBar toolbar = new ToolBar();
		selectionField = createSelectionField();
		toolbar.add(selectionField);
		if (selectionField instanceof DkDynamicAsyncPagingCombobox || selectionField instanceof DkComboBox)
		{
			selectionField.setWidth(DkBaseMain.config().getReferenceEditorComboboxWidth());
			if (selectionField instanceof DkDynamicAsyncPagingCombobox)
			{
				((DkDynamicAsyncPagingCombobox<M>) selectionField).getPagingToolBar().setShowMenu(false);
			}
		}
		toolbar.add(new FillToolItem());
		buttonAdd = new TextButton(DkBaseMain.i18n().label_add(), new SelectEvent.SelectHandler()
		{
			@Override
			public void onSelect(final SelectEvent event)
			{
				DkTimerUtils.executeDelayed(new DkDelayedExecution()
				{
					@Override
					public void run()
					{
						onAddButtonClicked();
					}
				});
			}
		});
		buttonAdd.setIcon(DkBaseMain.icons().file_plus_16());

		toolbar.add(buttonAdd);
		toolbar.setHeight(29);
		verticalLayoutContainer.add(toolbar, new VerticalLayoutData(1, 29));
		verticalLayoutContainer.add(grid, new VerticalLayoutData(1, 1));
	}

	protected abstract Field<M> createSelectionField();

	/**
	 * Method to be overridden to provide custom behavior
	 * 
	 * @param model
	 * @param column
	 * @param row
	 * @param modelKey
	 * @return
	 */
	protected boolean isModelDeletable(final M model, final int column, final int row, final String modelKey)
	{
		return true;
	};

	/**
	 * Enable or disable buttons
	 * 
	 * @param enabled
	 */
	@Override
	public void setEnabled(final boolean enabled)
	{
		buttonAdd.setEnabled(enabled);
		selectionField.setEnabled(enabled);
		this.enabled = enabled;
		DkUIUtils.setGridContentEnabled(grid, enabled, disabledStyle);
	}

	@Override
	public final boolean isEnabled()
	{
		return enabled;
	}

	/**
	 * @return the delete tooltip
	 */
	public abstract String getDeleteTooltip();

	/**
	 * Set the add button tooltip
	 * 
	 * @param tooltip
	 *           the tooltip
	 */
	public void setAddTooltip(final String tooltip)
	{
		if (buttonAdd != null)
		{
			buttonAdd.setTitle(tooltip);
		}
	}

	/**
	 * Set the add button label
	 * 
	 * @param label
	 *           the label
	 */
	public void setAddLabel(final String label)
	{
		if (buttonAdd != null)
		{
			buttonAdd.setText(label);
		}
	}

	/**
	 * Delete button clicked
	 */
	protected void onDeleteButtonClicked(final M model)
	{
		store.remove(model);
	}

	/**
	 * Add button clicked
	 */
	protected void onAddButtonClicked()
	{
		final M model = getSelectionField() instanceof ComboBox ? ((ComboBox<M>) getSelectionField()).getCurrentValue()
				: getSelectionField().getValue();
		if (model != null && !store.getAll().contains(model))
		{
			store.add(model);
		}
		resetFieldSelection();
	}

	/**
	 * @return the store
	 */
	public ListStore<M> getStore()
	{
		return store;
	}

	/**
	 * @return the grid
	 */
	public Grid<M> getGrid()
	{
		return grid;
	}

	/**
	 * @return the combobox
	 */
	public Field<M> getSelectionField()
	{
		return selectionField;
	}

	@Override
	public List<M> getValue()
	{
		return store.getAll();
	}

	/**
	 * Method not called in edition process. The editor/driver will call setValue on the store directly.
	 */
	@Override
	public void setValue(final List<M> value)
	{
		store.clear();
		if (value != null && value.size() > 0)
		{
			store.addAll(value);
		}
		resetFieldSelection();
	}

	/**
	 * Reset the combo box selection.
	 */
	protected void resetFieldSelection()
	{
		getSelectionField().clear();
	}

	/**
	 * @return the buttonAdd
	 */
	public TextButton getButtonAdd()
	{
		return buttonAdd;
	}

	/**
	 * @return this editor grid {@link ContentPanel}
	 */
	public ContentPanel getContentPanel()
	{
		return (ContentPanel) getWidget();
	}

	/**
	 * Sets the heading of the {@link ContentPanel}
	 * 
	 * @param heading
	 *           the heading to be set
	 */
	public void setHeadingHtml(final String heading)
	{
		getContentPanel().setHeaderVisible(true);
		getContentPanel().setHeadingHtml(heading);
		headerVisible = true;
	}

	/**
	 * @return the headerVisible
	 */
	public boolean isHeaderVisible()
	{
		return headerVisible;
	}

	/**
	 * Editor.forceLayout -> ResizeContainer.forceLayout -> SimpleContainer.forceLayout -> ResizeContainer.applyLayout ->
	 * ContentPanel.setPixelSize<br>
	 * 
	 * if(lastSize != new size) ContentPanel.onResize -> Dans cette methode on a getContainerTarget().setHeight(...)<br>
	 * <br>
	 * The ContentPanel onResize method was not called when the ContentPanel becomes visible due to size caching
	 */
	@Override
	public void setVisible(final boolean visible)
	{
		if (visible && !isVisible())
		{
			getContentPanel().clearSizeCache();
		}
		super.setVisible(visible);
	}

}
